//------------------------------------------------------------------------
// MJDEV JS API, base object and library
//------------------------------------------------------------------------

/*
 * Extended Date functionality
 */

/*
 * helper function in Date and others
 */	

if (typeof(String.capitalise)==='undefined') {

	String.prototype.capitalise = function () {

		return this.substr(0,1).toUpperCase() + this.substr(1);

	};

};

if (typeof(Date.add)==='undefined') {

	Date.prototype.add = function (p_Interval, p_Number) {

		var thing = new String();

		//in the spirt of VB we'll make this function non-case sensitive 
		//and convert the charcters for the coder. 
		p_Interval = p_Interval.toLowerCase(); 

		if (isNaN(p_Number)) {

			//Only accpets numbers
			//throws an error so that the coder can see why he effed up
			throw "The second parameter must be a number. \n You passed: " + p_Number;
			return false; 

		}

		p_Number = new Number(p_Number);

		switch (p_Interval.toLowerCase()) {

			case "yyyy": {// year
				this.setFullYear(this.getFullYear() + p_Number);
				break;
			}

			case "q": {		// quarter
				this.setMonth(this.getMonth() + (p_Number*3));
				break;
			}

			case "m": {		// month
				this.setMonth(this.getMonth() + p_Number);
				break;
			}

			case "y":		// day of year
			case "d":		// day
			case "w": {		// weekday
				this.setDate(this.getDate() + p_Number);
				break;
			}

			case "ww": {	// week of year
				this.setDate(this.getDate() + (p_Number*7));
				break;
			}

			case "h": {		// hour
				this.setHours(this.getHours() + p_Number);
				break;
			}

			case "n": {		// minute
				this.setMinutes(this.getMinutes() + p_Number);
				break;
			}

			case "s": {		// second
				this.setSeconds(this.getSeconds() + p_Number);
				break;
			}

			case "ms": {		// second
				this.setMilliseconds(this.getMilliseconds() + p_Number);
				break;
			}

			default: {
				//throws an error so that the coder can see why he effed up and
				//a list of elegible letters.
				throw	"The first parameter must be a string from this list: \n" + 
						"yyyy, q, m, y, d, w, ww, h, n, s, or ms.  You passed: " + p_Interval;
				return false;
			}

		}

		return this;

	};

};

if (typeof(Date.fromISO)==='undefined') {

	Date.prototype.fromISO = function (datestring) {
		// 1 3 5 7 8 10 12 13 15 16 17
		// yyyy - mm - dd 'T' HH : MM : ss .ms 'Z' +/- HH : MM
		// ? ? ? ? ?
		var pattern = /^(\d{4})(-(\d{2})(-(\d{2})(T(\d{2}):(\d{2})(:(\d{2})(\.(\d+))?)?(Z|((\+|-)(\d{2}):(\d{2}))))?)?)?$/,
		match = pattern.exec(datestring),
		date = new Date(),
		hour,
		minute,
		aheadOfUtc;

		if(null === match) return false; // throw new Error("Invalid ISO String");

		// The bit-shifting is shorthand for `Number(m) || 0`
		date.setUTCFullYear(match[1] >> 0);
		date.setUTCMonth(match[3] ? (match[3] >> 0) - 1 : 0);
		date.setUTCDate(match[5] >> 0);
		date.setUTCHours(match[7] >> 0);
		date.setUTCMinutes(match[8] >> 0);
		date.setUTCSeconds(match[10] >> 0);
		date.setUTCMilliseconds(match[12] >> 0);

		// Adjust to UTC offset
		if (match[13] && match[13] !== "Z") {

			hour = match[16] >> 0;
			minute = match[17] >> 0;
			aheadOfUtc = (match[15] === "+");

			hour = hour * 60 * 60 * 1000;
			minute = minute * 60 * 1000;

			if (aheadOfUtc) {

				hour = -hour;
				minute = -minute;

			}

			// easy dateline wrapping
			date = new Date(date.valueOf() + hour + minute);

		}

		return date;

	};

};

/*
 * helper function to get first milisecond from a day
 */

if (typeof(Date.getDayBegin)==='undefined') {

	Date.prototype.getDayBegin = function () { 

		return new Date(this.getFullYear(), this.getMonth(), this.getDate(),0,0,0,0); };

};

/*
 * helper function to get date with combination of time
 */

if (typeof(Date.getDateCombTime)==='undefined') {

	Date.prototype.getDateCombTime = function (date, hours, minutes, seconds, milisec) {

		return new Date(date.getFullYear(), date.getMonth(), date.getDate(),hours,minutes,seconds,milisec); 

	};

};

/*
 * helper zero align function
 * max 32 chars
 */

if (typeof(Date.to00)==='undefined') {

	Date.prototype.to00 = function(i, n) { return(('00000000000000000000000000000000'+i).substr(-n));	};

};

/*
 * Array bug replacement
 */

if(typeof(Array.remove)==='undefined') {

	// Remove from array, an error to remove object from array fix
	Array.prototype.remove = function(from, to) {

		var rest = this.slice((to || from) + 1 || this.length);
		this.length = from < 0 ? this.length + from : from;
		return this.push.apply(this, rest);

	};

}

/*
 * Infinite Array implementation, a qeque array in loop
 */

if (!(IArray instanceof Object)) {

	var IArray = function () {

		this.data = [];

	};


	/*
	 * undestructive pop, or just a shift of elements as needed
	 */
	IArray.prototype.pop = function () {

		if(this.count()==0) return undefined;
		var i = this.data.shift();
		this.data.push(i);

		return(i);

	};

	/*
	 * reverse undestructive pop, or just a shift of elements as needed
	 */
	IArray.prototype.poprev = function () {

		if(this.count()==0) return undefined;
		var i = this.data.pop();
		this.data.unshift(i);

		return(i);

	};

	/*
	 * destructive pop
	 */
	IArray.prototype.dpop = function () {

		if(this.count()==0) return undefined;

		return(this.data.pop());

	};

	/*
	 * get element from array by idx, undestructive
	 */
	IArray.prototype.get = function (idx) {

		if(this.data.length==0) return undefined;

		var i = idx;
		// if there no item exists, try to go from a array begin
		while(i>(this.data.length-1)) i=i-this.data.length;

		return this.data[i];

	};

	/*
	 * push item to an array
	 */
	IArray.prototype.push = function (item) { this.data.push(item); };

	/*
	 * count of elements
	 */
	IArray.prototype.count = function (item) { return this.data.length; };

	/*
	 * Clear all items
	 */
	IArray.prototype.clear = function (item) { this.data = []; };

	// Remove from array
	IArray.prototype.remove = function(from, to) {

		this.data.remove(from, to);

	};

};

//------------------------------------------------------------------------

/*
 * mjdevjs a base API
 */

if (!(mjdevjs instanceof Object)) {

	/**
	 * This represents the mjdevjs API itself, and provides a global namespace for accessing
	 * information about the state of mjdevjs.
	 * @class
	 */

	var mjdevjs = {

		queue: {
			ready: true,
			commands: [],
			timer: null
		},

		handlers : new IArray()

	};

	/*
	 * Unfocus all
	 */

	mjdevjs.unfocusAllHandlers = function () {

		for (var i=0; i<this.handlers.count(); i++) {

			var handler = this.handlers.get(i);
			handler.onUnFocus.apply(handler);
			handler.focused = false;

		}

	};

	/*
	 * focus last possible element
	 */

	mjdevjs.focusLastHandler = function () {

		this.unfocusAllHandlers();

		var last_focused = this.handlers.pop();
		last_focused.onFocus.apply(last_focused);

	};

	/*
	 * Event handler for global scope, whole app, for components to get key bindings
	 * 
	 * params:
	 * context : context in which handler will be called
	 * handler : object with onfocus(), onUnfocus() and onKey() functions, event handlers which will be called 
	 * g	   : guid for remove handler if needed 
	 */

	mjdevjs.registerHandler = function (handler, g) {

		if(typeof(handler)==='undefined') return;
		
		handler.g = g;
		this.handlers.push(handler);

	};

	/*
	 * removes handler
	 */

	mjdevjs.unRegisterHandler = function (g) {

		// remove
		for (var i in this.handlers.data) {

			if(this.handlers.data[i].g==g) {

				this.handlers.remove(i);
				return true;

			}

		}

		// focus previous
		this.focusLastHandler();

	};

	/*
	 * Helper function to create DOM element, set his id and base style
	 * 
	 * params important:
	 * type a type of an element see below
	 * params:
	 * id an DOM element id
	 * css - css style of created element
	 * append - if set, element will be appended to DOM element in this property
	 */

	mjdevjs.DOM_TYPE_DIV		= 'div';
	mjdevjs.DOM_TYPE_IMG		= 'img';
	mjdevjs.DOM_TYPE_SPAN		= 'span';
	mjdevjs.DOM_TYPE_INPUT		= 'input';
	mjdevjs.DOM_TYPE_SELECT		= 'select';
	mjdevjs.DOM_TYPE_UL		= 'ul';
	mjdevjs.DOM_TYPE_LI		= 'li';
	mjdevjs.DOM_TYPE_A		= 'a';
	mjdevjs.DOM_TYPE_P		= 'p';
	mjdevjs.DOM_TYPE_BUTTON		= 'button';
	mjdevjs.DOM_TYPE_PASSWORD	= 'password';


	// get confg from json
	mjdevjs.config = function(obj, cfg){

		if ((obj instanceof Object)&&(cfg instanceof Object)) 
			for(var i in cfg) obj[i] = cfg[i];

	};

	mjdevjs.me = function(config) {

		if(typeof(config)!=='object') return;
		if(typeof(config.type)==='undefined') return;
		if(typeof(config.append)==='undefined') {

			if(typeof(mjdevjs._body)!=='undefined') config.append = mjdevjs._body;
			else return;

		}

		if(config.type==mjdevjs.DOM_TYPE_PASSWORD) {
			var e = document.createElement(mjdevjs.DOM_TYPE_INPUT);
			e.setAttribute('type','password');
		} else {
			var e = document.createElement(config.type);
		}

		e.config = config;

		// for animations
		e.is_animated = false;

		// set id of element
		e.setId = function(id) { this.setAttribute('id',id); };

		// get id of element
		e.getId = function() { return this.getAttribute('id'); };

		// get dom element from this element by id		
		e.get = function (id) { return this.getElementById(id); };

		// set style
		e.setEStyle = function(st) { e.setAttribute('style', st); };

		// get style
		e.getEStyle = function() { return e.getAttribute('style'); };

		// set css style name
		e.setCss = function(css){ this.setAttribute('class',css); };

		// get css style name
		e.getCss = function(){ return this.getAttribute('class'); };

		//show element
		e.show = function () { this.style.display = 'block'; };

		//hide element
		e.hide = function () { this.style.display = 'none'; };
		
		// get/set html
		e.html = function(html) {

			if(typeof(html)!=='undefined') this.innerHTML = html;
			else return this.innerHTML;

		};

		if(typeof(config.html)!=='undefined') {

			e.html(config.html);
			delete(config.html);
		
		}

		config.append.appendChild(e);

		delete(config.append);
		delete(config.type);
		
		if(typeof(config.class)==='undefined')
			if(typeof(config.id)!=='undefined') config.class = config.id;

		for(var i in config) e.setAttribute(i,config[i]);

		if(config.type=='select') {

			e.addOption = function(name, value) {

				this.innerHTML += '<option name="'+ name +'" value="'+ name +'">'+ name +'</option>';

			};

		}

		// center element on screen or parent element if assigned in params
		e.center = function (parentEl) {

			this.moveTo(
				((parentEl||document).clientWidth - this.clientWidth)/2,
				((parentEl||document).clientHeight - this.clientHeight)/2
			);
	
		};

		// move element by coords
		e.moveTo = function (x,y) {
			// set position
			if (x) this.style.left = x+'px';
			if (y) this.style.top  = y+'px';
		};

		// scroll element body
		e.scrollTo = function (x,y) {
			if (x) this.scrollLeft = x;
			if (y) this.scrollTop  = y;
		};

		//set opacity
		e.setOpacity = function (value) { this.style.opacity = value ; }

		// fade out element
		e.fadeOut = function (fadeSteps, fadeTime, on_finish_handler, context) {

			if (typeof(this.is_animated)==='undefined') this.is_animated = false;

			if (!this.is_animated) {

				if (typeof(fadeSteps)!=='undefined') {

					this.anim_step = 0;
					this.anim_steps = fadeSteps;
					this.anim_step_d = 1/fadeSteps;
					this.anim_on_finish = mjdevjs.close((context||this), (on_finish_handler||function(){}));
					this.setOpacity(1);
					this.is_animated = true;
					this.anim_interval = setTimeout(mjdevjs.close(this, this.fadeOut), fadeTime);

				}

			} else {

				if (this.anim_step >= this.anim_steps) {

					this.setOpacity(0);
					delete(this.is_animated);
					delete(this.anim_step);
					delete(this.anim_step_d);
					delete(this.anim_interval);
					this.anim_on_finish();
					delete(this.anim_on_finish);

				} else {

					this.anim_step++;
					this.setOpacity((this.anim_steps-this.anim_step)*this.anim_step_d);
					this.anim_interval = setTimeout(mjdevjs.close(this, this.fadeOut), fadeTime);

				}

			}

		};

		// fade in element
		e.fadeIn = function (fadeSteps, fadeTime, on_finish_handler, context) {

			if (typeof(this.is_animated)==='undefined') this.is_animated = false;

			if (!this.is_animated) {

				if (typeof(fadeSteps)!=='undefined') {

					this.anim_step = 0;
					this.anim_steps = fadeSteps;
					this.anim_step_d = 1/fadeSteps;
					this.anim_on_finish = mjdevjs.close((context||this), (on_finish_handler||function(){}));
					this.setOpacity(0);
					this.is_animated = true;
					this.anim_interval = setTimeout(mjdevjs.close(this, this.fadeIn), fadeTime);

				}

			} else {

				if(this.anim_step>=this.anim_steps) {

					this.setOpacity(1);
					delete(this.is_animated);
					delete(this.anim_step);
					delete(this.anim_step_d);
					delete(this.anim_interval);
					this.anim_on_finish();
					delete(this.anim_on_finish);

				} else {

					this.anim_step++;
					this.setOpacity(this.anim_step*this.anim_step_d);
					this.anim_interval = setTimeout(mjdevjs.close(this, this.fadeIn), fadeTime);

				}

			}

		};

		/*
		 * a helper function to make name as is used for styles
		 */
		e.toCamelCase = function (sInput) {

			var oStringList = sInput.split('-');

			if(oStringList.length == 1)  return oStringList[0];

			var ret = sInput.indexOf("-") == 0 ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) : oStringList[0];

			for(var i = 1, len = oStringList.length; i < len; i++) {
				var s = oStringList[i];
				ret += s.charAt(0).toUpperCase() + s.substring(1);
			};

			return ret;

		};

		// set style of element
		e.setStyle = function (style, value) { this.style[style] = value; };

		// get style of element
		e.getStyle = function (style) {

			var value = this.style[this.toCamelCase(style)];

			if(!value){

				if(document.defaultView) { 

					value = document.defaultView.getComputedStyle(this, "").getPropertyValue(style);

				} else {

					if(this.currentStyle)
						value = this.currentStyle[this.toCamelCase(style)];

				}

			}

			return value;

		};

		//returns element details in array, [x,y,w,h]
		e.getCoords = function () {

			var x = parseInt(e.style.left);

			// get base position, which should work in any browser
			// if class is set, no automatic computing is working in some browsers, see code below
			var y = parseInt(e.style.top);
			var w = parseInt(e.style.width);
			var h = parseInt(e.style.height);

			// if some of elements have class definition and no style
			// we need to search their data in styles
			x = x || parseInt(this.getStyle('left'));
			y = y || parseInt(this.getStyle('top'));
			w = w || parseInt(this.getStyle('width'));
			h = h || parseInt(this.getStyle('height'));

			// in styles also not found, element may be wrong, but we need to be clear
			if(isNaN(x)) x = 0;
			if(isNaN(y)) y = 0;
			if(isNaN(w)) w = 0;
			if(isNaN(h)) h = 0;

			return([x, y, w, h]);

		};

		// resize element
		e.resizeTo = function (w, h) {

			// this sets size if property is not false, useful for sizing only one axis
			if (w) e.style.width  = w+'px';
			if (h) e.style.height = h+'px';

		};

		return e;

	};

	/*
	 * Call function in their own namespace, or namespace you want
	 * parameters:
	 *  context where function should run
	 *  function
	 *  function parameters
	 */

	mjdevjs.close = function(context, func, params) { return function() { return func.apply(context, (params||arguments) ); }; };

	/**
	 * Custom pub-sub channel that can have functions subscribed to it
	 * example:
	 * usefull for multi handlers event triggering
	 * for an example to init objects in dom at some event or 
	 * a GUI object can handle event withouth sharing/bubbling it
	 * cause GUI object needs only handle it itself and take control if so, if is active, which can be done by setFocus/unsetFocus
	 */

	mjdevjs.Channel = function (type) {

		this.type = type;
		this.handlers = {};
		this.guid = 0;
		this.fired = false;
		this.enabled = true;

	};

	/**
	 * Subscribes the given function to the channel. Any time that 
	 * Channel.fire is called so too will the function.
	 * Optionally specify an execution context for the function
	 * and a guid that can be used to stop subscribing to the channel.
	 * Returns the guid.
	 */

	mjdevjs.Channel.prototype.subscribe = function(f, c, g, l) {

		if (f === null) { return; } // need a function to call

		var func = f;

		if (typeof c === "object" && f instanceof Function) { func = mjdevjs.close(c, f); }

		g = g || func.observer_guid || f.observer_guid || this.guid++;
		func.observer_guid = g;
		f.observer_guid = g;

		// locking
		func.locked		= false;
		func.can_lock	= true;
		if(typeof(l)!=='undefined') func.can_lock = l;

		this.handlers[g] = func;

		return g;

	};

	/**
	 * Like subscribe but the function is only called once and then it
	 * auto-unsubscribes itself.
	 */

	mjdevjs.Channel.prototype.subscribeOnce = function(f, c) {

		var g = null;
		var _this = this;

		var m = function() {

			f.apply(c || null, arguments);
			_this.unsubscribe(g);

		};

		if (this.fired) {

			if (typeof c === "object" && f instanceof Function)

				f = mjdevjs.close(c, f);
				f.apply(this, this.fireArgs);

			} else g = this.subscribe(m);

		return g;

	};

	/** 
	 * Unsubscribes the function with the given guid from the channel.
	 */

	mjdevjs.Channel.prototype.unsubscribe = function(g) {

		if (g instanceof Function)  g = g.observer_guid;
		this.handlers[g] = null;
		delete this.handlers[g];

	};

	/** 
	 * Calls all functions subscribed to this channel.
	 */

	mjdevjs.Channel.prototype.fire = function(e) {

		if (this.enabled) {

			var fail = false;
			var item, handler, rv;

			for (item in this.handlers) {

				if (this.handlers.hasOwnProperty(item)) {

					handler = this.handlers[item];
					if ((handler instanceof Function)&&(!handler.locked)) {

						rv = (handler.apply(this, arguments) === false);
						fail = fail || rv;

					}

				}

			}

			this.fired = true;
			this.fireArgs = arguments;

			return !fail;

	    }

		return true;

	};

	/*
	 * Lock all handlers, and leave only selected unlocked (used for modal windows, and to take control of event for only one handler if needed)
	 */

	mjdevjs.Channel.prototype.lockAll = function(g) {

		var item, handler, rv;

		for (item in this.handlers) {

			if (this.handlers.hasOwnProperty(item)) {

				handler = this.handlers[item];
				if ((handler instanceof Function) && (handler.observer_guid != g) && (handler.can_lock)) 
					handler.locked = true;

			}

		}

		return true;

	};

	/*
	 * Unlock all handlers, and leave only selected unlocked (used for modal windows, and to take control of event for only one handler if needed)
	 */

	mjdevjs.Channel.prototype.unLockAll = function() {

		var item, handler, rv;

		for (item in this.handlers) {

			if (this.handlers.hasOwnProperty(item)) {

				handler = this.handlers[item];
				if (handler instanceof Function) handler.locked = false;

			}

		}

		return true;

	};

	/**
	 * Calls the provided function only after all of the channels specified
	 * have been fired.
	 */

	mjdevjs.Channel.join = function(h, c) {

		var i = c.length;
		var f = function() { if (!(--i)) h(); };
		var len = i;
		var j;

		for (j=0; j<len; j++) {

			if (!c[j].fired) c[j].subscribeOnce(f);
			else i--;

		}

		if (!i) h();

	};

	/*
	 * include script on demand, dynamicaly
	 * usefull to load device driver for device control
	 */

	mjdevjs.includeJavascript = function(jsfile, successCallback, context) {

		var id = document.getElementsByTagName("head")[0];         
		var el = document.createElement('script');

		el.type = 'text/javascript';
		el.src = jsfile;
		id.appendChild(el);

		if (typeof successCallback !== 'undefined') el.onload = function() {

			return successCallback.apply(context);

		};

	};

	/*
	 * load object script if object does not already exists
	 */

	mjdevjs.requireComponent = function(compName, successCallback, context) {

		if (!((window[compName.capitalise()] instanceof Object)&&(window[compName.capitalise()] instanceof Function)))
			this.includeJavascript('component/' + compName.toLowerCase() + '.js', successCallback, context);
		else
			if (typeof successCallback !== 'undefined') return successCallback.apply(context);

	};

	/*
	 * function will load component if exists or not
	 */

	mjdevjs.loadComponent = function (compName, callback) {

		mjdevjs.includeJavascript('component/'+compName+'.js', callback, window);

	};

	/*
	 * Lazy  load ajax cache
	 * an asynchronous data loading object
	 * 
	 * to do :  memory management and checks
	 * application should call gc(), but thats slow action, be care !
	 */

	mjdevjs.AjaxObject = function (url, callbackFunction, context) {

		var that=this; // scope recovery inside ajax object      
		var urlCall = url;

		this.updating = false;
		this.callback = callbackFunction || function () {};
		this.conttext = context || window;

		this.update = function (passData, postMethod) { 
			var AJAX = null;

			// if object is in updating state, means not loaded completely, return false
			if (that.updating==true) return false;

			that.updating=true;

			// create element depending on browser type
			AJAX = new XMLHttpRequest();              

			// if object creation fail return false
			if (AJAX==null) return false;                               
			else {

				// if data received
				AJAX.onreadystatechange = function () {  

					if (AJAX.readyState==4) {

						that.updating = false; 

						var ct = (AJAX.getResponseHeader('Content-Type')).replace('text/','');

						// call callback function with data, depending of data type
						if(ct=='xml')
							that.callback.apply(context, [AJAX.status, AJAX.responseXML, AJAX.getAllResponseHeaders(), ct]);
						else
							that.callback.apply(context, [AJAX.status, AJAX.responseText, AJAX.getAllResponseHeaders(), ct]);

						delete AJAX; // cleanup

					}

				};

				// date value as a random number
				//var timestamp = new Date();  
				// othervise call method with random generated value
				var uri = urlCall;//+'?'+passData+'×tamp='+(timestamp*1); 
				AJAX.open("GET", uri, true);
				AJAX.send(null);

				return true;

			}

		};

	};

	// to do logic for streaming content from/to server
	// implementation depends on device possibility

	// by timer
	// content can be asynchroniously prepared withouth events or something else
	// just put element to cache and wait for callback call
	// not done cause of possible indisability device to work with setTimeout

	// by demand

	mjdevjs.cache = {
		data : [],

		/*
		 * function to get remote content, which can be scheduled 
		 */

		getRemoteContent : function (id, url, callback, context, schedule) {

			if(this.data.hasOwnProperty(id)) {

				// object exists, call callback
				var cb = (callback||false);
				var cc = (context||window);
				if(cb) cb.apply(cc, [this.data[id].id, this.data[id].data]);

			} else {

				// create new object
				this.data[id] = {

					'id'			: id,
					'url'			: url,
					'data'			: {},
					'status'		: 0,
					'callback' 	 	: (callback || false),
					'context'		: (context || window),
					'headers'		: [],
					'content_type'	: ''

				}; 

				// get data from server
				(new mjdevjs.AjaxObject(url, function (status, data, headers, ct) {

					this.status	= status;
					this.headers = headers;
					this.content_type = ct;

					if(ct=='xml') { // got xml data

						mjdevjs.requireComponent('XMLObjectifier', function(){

							try { this.data = XMLObjectifier.xmlToJSON(data); } catch (e) { this.data = undefined; };
							if(this.callback) this.callback.apply(this.context, [this.id, this.data]);

						}, this);

					} else if (ct=='json') { // got json data

						try { this.data = eval(data); } catch (e) { this.data = undefined; };
						if(this.callback) this.callback.apply(this.context, [this.id, this.data]);

						return true;

					} else { // no data retrieved

						try { this.data = eval(data); } catch (e) { this.data = undefined; };
						if(this.callback) this.callback.apply(this.context, [this.id, this.data]);

						return true;

					}

				},this.data[id])).update();

			}

		},

		getContent : function (id, callback, context) {

			if(this.data.hasOwnProperty(id)) {

				// object exists, call callback
				var cb = (callback||false);
				var cc = (context||window);
				if(cb) cb.apply(cc, [this.data[id].id, this.data[id].data]);

			} else {
// to do
//error no content found

			}

		}

	};

	mjdevjs.createUUID = function() {
		return mjdevjs.UUIDcreatePart(4) + '-' +
			mjdevjs.UUIDcreatePart(2) + '-' +
			mjdevjs.UUIDcreatePart(2) + '-' +
			mjdevjs.UUIDcreatePart(2) + '-' +
			mjdevjs.UUIDcreatePart(6);
	};

	mjdevjs.UUIDcreatePart = function(length) {
		var uuidpart = "";
		var i, uuidchar;
		for (i=0; i<length; i++) {
			uuidchar = parseInt((Math.random() * 256),0).toString(16);
			if (uuidchar.length === 1) uuidchar = "0" + uuidchar;
			uuidpart += uuidchar;
		}
		return uuidpart;
	};

/*
 * INIT LIBRARY 
 */
	/*
	 * This events must be defined here
	 * base event for application and library
	 */

	// tells application to make all initialisation needed
	mjdevjs.onAppStart	= new mjdevjs.Channel('AppStart');

	// tells library that application is initialised
	mjdevjs.onAppReady	= new mjdevjs.Channel('AppReady');

	// tells library that device is ready to work, all key events mapped
	mjdevjs.deviceReady = new mjdevjs.Channel('DeviceReady');

	// this is event to get device control requests
	mjdevjs.onKeyDown	= new mjdevjs.Channel('KeyDown');

	// tells library to flush resources and make app paused
	mjdevjs.onAppPause 	= new mjdevjs.Channel('AppPause');

	// tells library to stop working and flush completely all resources 
	mjdevjs.onAppStop  	= new mjdevjs.Channel('AppStop');

	/*
	 * app ready event is return by application if all initialisation procedures and component stuffs are done
	 * to tell library that it can start to handle events from device
	 */
	mjdevjs.onAppReady.subscribeOnce(function(){

		// focus first element, with key event handler in stack
		var handler = mjdevjs.handlers.get(0);
		if(handler) handler.onFocus.apply(handler);

	},mjdevjs,'app-ready');

	/*
	 * library init function
	 */

	mjdevjs.init = function() {
		this._body = document.getElementsByTagName('body')[0];

		// resize and show log
		if(typeof(log)!=='undefined') {

			log.resize('bb-large');
			log.show();

			window.onerror = function (e) {

				log.error(e);
				return false;

			};

		} else {

			window.log = {

				info	: function (){},
				resize	: function (){},
				hide	: function (){},
				debug	: function (){},
				error	: function (){},
				show	: function (){}

			};

		};

		this.requireComponent('DeviceInfo', function() {

			this.requireComponent('DevControl', function() {

				// initialise device and all object needed
				this.devControl = new DevControl();

				// library ready and keyboard mapping ready event
				mjdevjs.deviceReady.subscribeOnce(function(){

					// device control request
					mjdevjs.onKeyDown.subscribe(function(key) {

						// log
						//log.info( 'got key : ('+ key +','+ KBD_MAP.getKeyName(key) +')');

						// get last handled element
						var handler = mjdevjs.handlers.get(0);

						if(typeof(handler)==='undefined') return true;

						// is focused by ENTER key?
						if (handler.focused) {

							// handler focused, need to capture events
							return handler.onKey.apply(handler, [key]);

						} else {

							// no item focused, focus/unfocus them on demand
							switch (KBD_MAP.getKeyName(key)) {

								case 'VK_LEFT' : // next element to focus
									handler.onUnFocus.apply(handler);
									mjdevjs.handlers.pop();
									handler = mjdevjs.handlers.get(0);
									if(typeof(handler)==='undefined') return;
									handler.onFocus.apply(handler);
									return false;
									break;

								case 'VK_RIGHT' : // prev element to focus
									handler.onUnFocus.apply(handler);
									mjdevjs.handlers.poprev();
									handler = mjdevjs.handlers.get(0);
									if(typeof(handler)==='undefined') return;
									handler.onFocus.apply(handler);
									return false;
									break;

								case 'VK_UP' : // next element to focus
									handler.onUnFocus.apply(handler);
									mjdevjs.handlers.pop();
									handler = mjdevjs.handlers.get(0);
									if(typeof(handler)==='undefined') return;
									handler.onFocus.apply(handler);
									return false;
									break;

								case 'VK_DOWN' : // prev element to focus
									handler.onUnFocus.apply(handler);
									mjdevjs.handlers.poprev();
									handler = mjdevjs.handlers.get(0);
									if(typeof(handler)==='undefined') return;
									handler.onFocus.apply(handler);
									return false;
									break;

								case 'VK_ENTER' : // enter/ok, means give device control to element, until he give focus back
									// handler get focus, all other lose
									for(var i in mjdevjs.handlers) mjdevjs.handlers[i].focused = false;
									handler.focused = true;
									return false;
									break;

								case 'VK_DEBUG' :
									log.toggle();
									return false;
									break;

								default : 
									//alert(KBD_MAP.getKeyName(key));
									return true;

							}

						}

					}, this, 'mjdevjs-key-event');

					this.requireComponent('Settings', function() {	

						// load global settings
						this.settings = new Settings(function(){

							//this.requireComponent('Alert', function() {

								//window.messages = new Alert();
								//window.messages.create('alert', document.getElementsByTagName('body')[0]);

								//window.alert = function (txt) {

									//window.messages.add(txt);
									//window.messages.show();

								//};

								// fire event to start an application, library ready
								mjdevjs.onAppStart.fire();

							//}, this);

						}, this);

					}, this);

				}, this);

			}, this);

		}, this);
		return true;
	};

}

//------------------------------------------------------------------------
